#pragma once
/*
 * rpc handler.  
 */
#include <string>
#include <tuple>
#include <utility>
#include <unordered_map>
#include "msgpack-c/msgpack.hpp"
#include "rpc/rpc_stream.hpp"
#include "rpc/function_traits.hpp"

namespace anet {
	namespace rpc_codec {
		constexpr int kHeadSize   = 4; // Head size
		constexpr int kMethodSize = 4; // Method size.

		 // register member function.
         #define registerFunc(memberFunc) this->register_func(#memberFunc, &memberFunc)

		// build remote call: method and its arguments.
		template <typename... Args>
		inline void pack_remote_call(rpc_stream &stream, const std::string& method, Args&&... args) {
			// pack args.
			auto t = std::make_tuple(std::forward<Args>(args)...);

			// 2. write header.
			unsigned int totalSize = 0;
			stream.write_stream().write((char*)&totalSize, kHeadSize);

			// 3. write method.
			unsigned int methodSize = (unsigned int)method.size();
			stream.write_stream().write((char*)&methodSize, kMethodSize);
			stream.write_stream().write(method.c_str(), methodSize);

			// 4. pack the function's parameter with msgpack.
			try {
				msgpack::pack(stream.write_stream(), t);
			} catch (...) {
				return;
			}

			// add tcp header.
			totalSize = (unsigned int)(stream.buf().size());
			unsigned int allLen = (unsigned int)(htonl(totalSize));
			stream.write_stream().seekp(0, std::ios_base::beg);
			stream.write_stream().write((char*)&allLen, kMethodSize);

			// skip to end.
			stream.write_stream().seekp(totalSize + kHeadSize, std::ios_base::beg);
		}

		// unpack method and it arguments.
		inline std::pair<std::tuple<char*,int,char*,int>, bool> 
			unpack_remote_call(rpc_stream &stream, const char* msg, int size) {
			// write message data.
			stream.write_stream().write(msg, size);

			// get method size.
			stream.read_stream().seekg(kHeadSize, std::ios_base::beg);
			unsigned int methodSize = 0;
			stream.read_stream().read((char*)(&methodSize), kMethodSize);

			int paraSize = size - kMethodSize - methodSize;
			if (paraSize < 0) {
				return { std::tuple<char*,int,char*,int>{}, false };
			}

			try {
				// return method and arguments string.
				char* p = stream.c_str();
				return { std::make_tuple(p, methodSize, p+methodSize, paraSize),true };
			} catch (...) {
				return { std::tuple<char*,int,char*,int>{}, true };
			}
		}

		// Primary template to skip the first type and create a new tuple
		template <typename Tuple, std::size_t... Indices>
		inline auto tail_impl(Tuple&& tuple, std::index_sequence<Indices...>) {
			return std::make_tuple(std::get<Indices + 1>(std::forward<Tuple>(tuple))...);
		}

		// Helper template to generate the index sequence
		template <typename Tuple>
		inline auto tail(Tuple&& tuple) {
			constexpr std::size_t N = std::tuple_size<std::remove_reference_t<Tuple>>::value;
			static_assert(N >= 1, "Tuple must have at least 2 elements");
			return tail_impl(std::forward<Tuple>(tuple), std::make_index_sequence<N - 1>());
		}

		// handler trait.
		template <typename Cls, typename Session>
		struct handler_base {
			virtual void call(Cls*, Session*, const std::string&) = 0;
		};

		// handler implementation.
		template <typename Cls, typename Handler, typename Session>
		struct handler_impl : public handler_base<Cls,Session> {
			handler_impl(Handler& handler) : m_handler(handler) {
			}
			~handler_impl() {}

			virtual void call(Cls* self, Session* session, const std::string& args) {
				using args_tuple = typename function_traits<Handler>::tuple_type;
				args_tuple t;
				auto nextT = tail(t);
				try {
					msgpack::unpack(args.c_str(), args.size()).get().convert(nextT);
				} catch (...) {
					// handle exception.
					throw std::invalid_argument("unpack failed: Args not match!");
					return;
				}
				using index = std::make_index_sequence<std::tuple_size<std::remove_reference_t<decltype(nextT)>>::value>;
				call_impl(self, session, nextT, index{});
			}

			template<typename TupleArgs, std::size_t ... Is>
			void call_impl(Cls* self, Session *session, TupleArgs&& tuple_args, std::index_sequence<Is...>) {
				(self->*m_handler)(session, std::forward<typename std::tuple_element<Is, std::remove_reference_t<TupleArgs>>::type>(std::get<Is>(tuple_args))...);
			}

			Handler m_handler;
		};

		// Derive is handler class, and Session is the first parameter of the processing function.
		// such as rpcTest(Session *session, ...);
		template <typename Derive, typename Session>
		class handle_service {
		public:
			typedef Derive subclass;
			typedef handler_base<subclass, Session> handler_cls;
			typedef std::shared_ptr<handler_cls> handler_cls_ptr;
			typedef std::unordered_map<std::string, handler_cls_ptr> func_map;

			handle_service() {
				m_registers.clear();
			}
			virtual ~handle_service() {
				m_registers.clear();
			}

		public:
			template <typename Handler>
			void register_func(const std::string& method, Handler&& handler) {
				typedef handler_impl<subclass, Handler, Session> handle_impl_type;
				handler_cls_ptr ptr = std::make_shared<handle_impl_type>(handler);
				m_registers.emplace(method, ptr);
			}

			// call method
			bool call_method(Session *session, const char *msg, int size) {
				if (session == nullptr || msg == nullptr || size <= 0) {
					return false;
				}

				rpc_stream stream;
				auto t = std::move(unpack_remote_call(stream, msg, size));
				if (!t.second) {
					return false;
				}

				auto& tuplePara = t.first;
				auto& method = std::get<0>(tuplePara);
				auto& methodSize = std::get<1>(tuplePara);
				auto& args = std::get<2>(tuplePara);
				auto& argsSize = std::get<3>(tuplePara);
				if (methodSize <= 0 || argsSize <= 0) {
					return false;
				}

				// method name.
				auto &&methodName = std::string(method, methodSize);
				auto it = m_registers.find(methodName);
				if (it != m_registers.end()) {
					try {
						auto &&argsPara = std::string(args, argsSize);
						it->second->call(dynamic_cast<subclass*>(this), session, argsPara);
						return true;
					} catch (const std::exception& e) {
						return false;
					}
				}
				return false;
			}

		protected:
			func_map m_registers;
		};
	}
}